#include <ctime>
#include "heightMap.h"
#include <string.h>

#ifndef max
#define max(a,b)            (((a) > (b)) ? (a) : (b))
#endif

#ifndef min
#define min(a,b)            (((a) < (b)) ? (a) : (b))
#endif

HeightMap::HeightMap() : sizeOfMap(0), gridSpace(0), m_heightScale(1.0f)
{
}

HeightMap::~HeightMap()
{
    destroy();
}

bool HeightMap::create(int size, int gridSpacing, float scale)
{
    m_heightScale = scale;
    sizeOfMap = size;
    gridSpace = gridSpacing;

    try
    {
        heightList.resize(sizeOfMap * sizeOfMap);
    }
    catch (const std::bad_alloc &)
    {
        return false;
    }

    memset(&heightList[0], 0, heightList.size());
    return true;
}

void HeightMap::destroy()
{
    m_heightScale = 1.0f;
    sizeOfMap = 0;
    gridSpace = 0;
    heightList.clear();
}

void HeightMap::createNewFractal(float roughness)
{
    srand(static_cast<unsigned int>(time(0)));

    std::fill(heightList.begin(), heightList.end(), 0.0f);

    int p1, p2, p3, p4, mid;
    float dH = sizeOfMap * 0.5f;
    float dHFactor = powf(2.0f, -roughness);
    float minH = 0.0f, maxH = 0.0f;

    for (int w = sizeOfMap; w > 0; dH *= dHFactor, w /= 2)
    {
        // Diamond Step
        for (int z = 0; z < sizeOfMap; z += w)
        {
            for (int x = 0; x < sizeOfMap; x += w)
            {
                p1 = heightIndexAt(x, z);
                p2 = heightIndexAt(x + w, z);
                p3 = heightIndexAt(x + w, z + w);
                p4 = heightIndexAt(x, z + w);
                mid = heightIndexAt(x + w / 2, z + w / 2);

                heightList[mid] = Math::random(-dH, dH) + (heightList[p1] + heightList[p2] + heightList[p3] + heightList[p4]) * 0.25f;

                minH = min(minH, heightList[mid]);
                maxH = max(maxH, heightList[mid]);
            }
        }

        // Square step
        for (int z = 0; z < sizeOfMap; z += w)
        {
            for (int x = 0; x < sizeOfMap; x += w)
            {
                p1 = heightIndexAt(x, z);
                p2 = heightIndexAt(x + w, z);
                p3 = heightIndexAt(x + w / 2, z - w / 2);
                p4 = heightIndexAt(x + w / 2, z + w / 2);
                mid = heightIndexAt(x + w / 2, z);

                heightList[mid] = Math::random(-dH, dH) + (heightList[p1] + heightList[p2] + heightList[p3] + heightList[p4]) * 0.25f;

                minH = min(minH, heightList[mid]);
                maxH = max(maxH, heightList[mid]);

                p1 = heightIndexAt(x, z);
                p2 = heightIndexAt(x, z + w);
                p3 = heightIndexAt(x + w / 2, z + w / 2);
                p3 = heightIndexAt(x - w / 2, z + w / 2);
                mid = heightIndexAt(x, z + w / 2);

                heightList[mid] = Math::random(-dH, dH) + (heightList[p1] + heightList[p2] + heightList[p3] + heightList[p4]) * 0.25f;

                minH = min(minH, heightList[mid]);
                maxH = max(maxH, heightList[mid]);
            }
        }
    }

    smooth();

	water_height = 255.0f * (15) / (maxH - minH);

    // Normalize height field so altitudes fall into range [0,255]
    for (int i = 0; i < sizeOfMap * sizeOfMap; ++i){
        heightList[i] = 255.0f * (heightList[i] - minH) / (maxH - minH);
	
		if(heightList[i] < water_height){
			 heightList[i] = water_height;
		}
	}

		
}

float HeightMap::heightAt(float x, float z) const
{
    x /= static_cast<float>(gridSpace);
    z /= static_cast<float>(gridSpace);

    long ix = Math::floatToLong(x);
    long iz = Math::floatToLong(z);
    float topLeft = heightList[heightIndexAt(ix, iz)] * m_heightScale;
    float topRight = heightList[heightIndexAt(ix + 1, iz)] * m_heightScale;
    float bottomLeft = heightList[heightIndexAt(ix, iz + 1)] * m_heightScale;
    float bottomRight = heightList[heightIndexAt(ix + 1, iz + 1)] * m_heightScale;
    float percentX = x - static_cast<float>(ix);
    float percentZ = z - static_cast<float>(iz);

    return Math::bilerp(topLeft, topRight, bottomLeft, bottomRight, percentX, percentZ);
}

void HeightMap::normalAt(float x, float z, Vector3 &n) const
{
    x /= static_cast<float>(gridSpace);
    z /= static_cast<float>(gridSpace);

    long ix = Math::floatToLong(x);
    long iz = Math::floatToLong(z);

    float percentX = x - static_cast<float>(ix);
    float percentZ = z - static_cast<float>(iz);

    Vector3 topLeft;
    Vector3 topRight;
    Vector3 bottomLeft;
    Vector3 bottomRight;
    Vector3 normal;

    normalAtPixel(ix, iz, topLeft);
    normalAtPixel(ix + 1, iz, topRight);
    normalAtPixel(ix, iz + 1, bottomLeft);
    normalAtPixel(ix + 1, iz + 1, bottomRight);

    n = Math::bilerp(topLeft, topRight, bottomLeft, bottomRight, percentX, percentZ);
    n.normalize();
}

void HeightMap::normalAtPixel(int x, int z, Vector3 &n) const
{
    if (x > 0 && x < sizeOfMap - 1)
        n.x = heightAtPixel(x - 1, z) - heightAtPixel(x + 1, z);
    else if (x > 0)
        n.x = 2.0f * (heightAtPixel(x - 1, z) - heightAtPixel(x, z));
    else
        n.x = 2.0f * (heightAtPixel(x, z) - heightAtPixel(x + 1, z));

    if (z > 0 && z < sizeOfMap - 1)
        n.z = heightAtPixel(x, z - 1) - heightAtPixel(x, z + 1);
    else if (z > 0)
        n.z = 2.0f * (heightAtPixel(x, z - 1) - heightAtPixel(x, z));
    else
        n.z = 2.0f * (heightAtPixel(x, z) - heightAtPixel(x, z + 1));

    n.y = 2.0f * gridSpace;
    n.normalize();
}

void HeightMap::blur(float amount)
{
    float *pPixel = 0;
    float *pPrevPixel = 0;
    int width = sizeOfMap;
    int height = sizeOfMap;
    int pitch = sizeOfMap;

    for (int i = 0; i < height; ++i)
    {
        pPrevPixel = &heightList[i * pitch];

        // Left-to-right
        for (int j = 1; j < width; ++j)
        {
            pPixel = &heightList[(i * pitch) + j];
            *pPixel = (*pPrevPixel * amount) + (*pPixel * (1.0f - amount));
            pPrevPixel = pPixel;
        }

        pPrevPixel = &heightList[(i * pitch) + (width - 1)];

        // Right-to-left
        for (int j = width - 2; j >= 0; --j)
        {
            pPixel = &heightList[(i * pitch) + j];
            *pPixel = (*pPrevPixel * amount) + (*pPixel * (1.0f - amount));
            pPrevPixel = pPixel;
        }
    }

    for (int i = 0; i < width; ++i)
    {
        pPrevPixel = &heightList[i];

        // Top-to-bottom
        for (int j = 1; j < height; ++j)
        {
            pPixel = &heightList[(j * pitch) + i];
            *pPixel = (*pPrevPixel * amount) + (*pPixel * (1.0f - amount));
            pPrevPixel = pPixel;
        }

        pPrevPixel = &heightList[((height - 1) * pitch) + i];

        // Bottom-to-top
        for (int j = height - 2; j >= 0; --j)
        {
            pPixel = &heightList[(j * pitch) + i];
            *pPixel = (*pPrevPixel * amount) + (*pPixel * (1.0f - amount));
            pPrevPixel = pPixel;
        }
    }
}

unsigned int HeightMap::heightIndexAt(int x, int z) const
{
    return (((x + sizeOfMap) % sizeOfMap) + ((z + sizeOfMap) % sizeOfMap) * sizeOfMap);
}

void HeightMap::smooth()
{
    std::vector<float> source(heightList);
    float value = 0.0f;
    float cellAverage = 0.0f;
    int i = 0;
    int bounds = sizeOfMap * sizeOfMap;

    for (int y = 0; y < sizeOfMap; ++y)
    {
        for (int x = 0; x < sizeOfMap; ++x)
        {
            value = 0.0f;
            cellAverage = 0.0f;

            i = (y - 1) * sizeOfMap + (x - 1);
            if (i >= 0 && i < bounds)
            {
                value += source[i];
                cellAverage += 1.0f;
            }

            i = (y - 1) * sizeOfMap + x;
            if (i >= 0 && i < bounds)
            {
                value += source[i];
                cellAverage += 1.0f;
            }

            i = (y - 1) * sizeOfMap + (x + 1);
            if (i >= 0 && i < bounds)
            {
                value += source[i];
                cellAverage += 1.0f;
            }

            i = y * sizeOfMap + (x - 1);
            if (i >= 0 && i < bounds)
            {
                value += source[i];
                cellAverage += 1.0f;
            }

            i = y * sizeOfMap + x;
            if (i >= 0 && i < bounds)
            {
                value += source[i];
                cellAverage += 1.0f;
            }

            i = y * sizeOfMap + (x + 1);
            if (i >= 0 && i < bounds)
            {
                value += source[i];
                cellAverage += 1.0f;
            }

            i = (y + 1) * sizeOfMap + (x - 1);
            if (i >= 0 && i < bounds)
            {
                value += source[i];
                cellAverage += 1.0f;
            }

            i = (y + 1) * sizeOfMap + x;
            if (i >= 0 && i < bounds)
            {
                value += source[i];
                cellAverage += 1.0f;
            }

            i = (y + 1) * sizeOfMap + (x + 1);
            if (i >= 0 && i < bounds)
            {
                value += source[i];
                cellAverage += 1.0f;
            }

            heightList[y * sizeOfMap + x] = value / cellAverage;
        }
    }
}
